package gzran

import (
	"compress/flate"
	"compress/gzip"
	"io"
	"reflect"
	"unsafe"
)

type Position struct {
	Origin int64
	File   int64
}

type IndexHeader struct {
	Position
	DictOffset int64
}

type Block struct {
	Position
	Byte    byte
	NumByte uint8
	Dict    []byte
}

type Offset struct {
	Position
	Block Block
	Debug *Debug
}

type Debug struct {
	Count      int
	MaxDict    int
	Size       int64
	CSize      int64
	FileSpan   int64
	OriginSpan int64
	MaxCDSize  int64
}

type Finder interface {
	Find() bool
}

func (o *Offset) updateOffset(d *decompressor) {
	o.Block.Origin = o.Origin
	o.Block.Byte = byte(d.b)
	o.Block.NumByte = uint8(d.nb)
	o.Block.File = d.roffset
	o.Block.Dict = d.dict.Hist[d.dict.RdPos:d.dict.WrPos]
}

type Reader struct {
	*gzip.Reader
	Offset *Offset
	hk     *hook
	//Index  *IndexReader
}

func (r *Reader) Seek(offset int64, whence int) (int64, error) {
	//if r.Index == nil {
	//	return 0, io.ErrShortBuffer
	//}

	return 0, io.EOF

}

type iReader interface {
	io.Seeker
}
//type IndexReader struct {
//	Indexer CustomIndexer
//	r       iReader
//	block   Block
//}
//
//type IndexWriter struct {
//	Indexer CustomIndexer
//}

//type CustomIndexer interface {
//	Encode(enc *msgpack.Encoder) error
//	Decode(enc *msgpack.Decoder) error
//}
//
//func (w *IndexWriter) WriteIndex(b *Block) (err error) {
//	enc := w.enc
//	err = enc.EncodeInt(b.Origin)
//	err = enc.EncodeInt(b.File)
//	if err == nil && w.Indexer != nil {
//		err = w.Indexer.Encode(w.enc)
//	}
//	if err != nil {
//		return err
//	}
//	v := uint16(b.Byte) | (uint16(b.NumByte) << 8)
//	err = enc.EncodeBytes(b.Dict)
//	err = enc.EncodeUint16(v)
//	return
//}
//func (r *IndexReader) ReadIndex(f Finder) (err error) {
//	r.block.Origin, err = r.dec.DecodeInt64()
//	r.block.File, err = r.dec.DecodeInt64()
//	if err == nil && r.Indexer != nil {
//		err = r.Indexer.Decode(r.dec)
//	}
//	if err != nil {
//		return
//	}
//	if !f.Find() {
//
//	}
//	return
//}

type hook struct {
	r           io.ReadCloser
	d           *decompressor
	stepP       *uintptr
	nextBlockFn uintptr
	offset      Offset
	dict        DictDecoder
}

func (h *hook) Reset(r io.Reader, dict []byte) error {
	return h.d.r.(flate.Resetter).Reset(r, dict)
}

type countWriter struct {
	count int
}

func newCountWriter() *countWriter {
	return &countWriter{}
}

func (c *countWriter) Write(p []byte) (n int, err error) {
	n = len(p)
	c.count += n
	return
}

func (h *hook) Read(b []byte) (n int, err error) {
	for {
		if len(h.d.toRead) > 0 {
			n = copy(b, h.d.toRead)
			h.d.toRead = h.d.toRead[n:]
			if len(h.d.toRead) == 0 {
				h.offset.Origin += int64(n)
				return n, h.d.err
			}
			h.offset.Origin += int64(n)
			return n, nil
		}
		if h.d.err != nil {
			return 0, h.d.err
		}
		// new block
		if *h.stepP == h.nextBlockFn && h.d.roffset-h.offset.Block.File >= 1024*64*4 {
			h.offset.updateOffset(h.d)
		}
		h.d.step(h.d)
		h.offset.File = h.d.roffset
		if h.d.err != nil && len(h.d.toRead) == 0 {
			h.d.toRead = h.d.dict.readFlush() // Flush what's left in case of error
		}
	}
}
func (h *hook) Close() error {
	if h.d.err == io.EOF {
		return nil
	}
	return h.d.err
}

func newHook(r io.ReadCloser) *hook {
	f := (*decompressor)(unsafe.Pointer(reflect.ValueOf(r).Pointer()))
	stepP := (*uintptr)(unsafe.Pointer(&f.step))
	return &hook{
		r:           r,
		d:           f,
		stepP:       stepP,
		nextBlockFn: *stepP,
	}
}

func NewReader(r io.Reader) (gz *Reader, err error) {
	gr, err := gzip.NewReader(r)
	if err != nil {
		return
	}
	ur := (*reader)(unsafe.Pointer(gr))
	hk := newHook(ur.decompressor)
	ur.decompressor = hk
	gz = &Reader{
		Reader: gr,
		Offset: &hk.offset,
		hk:     hk,
	}
	return
}

// SetReadOffset gzip Header len
func (r *Reader) SetReadOffset(len int) {
	r.hk.d.roffset = int64(len)
}
